##------------------------------------------------------------------------------
##
##	yasco-1.0 - Yet Another (Java)Script Obfuscator
##	Copyright (C) 2008-2011 Oliver Schieche <post@oliver-schieche.de>
##
##	Makes use of Yapp
##	Parse::Yapp - Copyright (c) 1998-2001, Francois Desarmenien, all right reserved.
##
##	$Id: Node.pm 12 2012-10-01 13:57:30Z goresplatter1844@gmail.com $
##------------------------------------------------------------------------------
package Yasco::Node;

use strict;
use Data::Dumper;
use Yasco::Filter::Id2Value;
##------------------------------------------------------------------------------
BEGIN {
	$Yasco::Node::err = 0;
	$Yasco::Node::format = 0;

	no strict 'refs';
	foreach my $k (qw/id op left right line next parent pre_space/) {
		*{__PACKAGE__ .'::'. $k} = sub {
			shift->{$k}
		}
	}

	$Yasco::Node::DOT_GLUE = '.';
	$Yasco::Node::rx_number = qw/^-?\d+(?:\.\d+)?$/;

	$Yasco::Node::id = 1;
	$Yasco::Node::nodes_lastperc = 0;
	$Yasco::Node::nodes_processed = 0;
	%Yasco::Node::skip_semi = map {$_=>1} qw/IF BLOCK FUNC_DECL SWITCH TRY WHILE WITH/;
	$Yasco::Node::op_txl8 = {
		'!'				=> '_UNARY',
		'DELETE'		=> '_UNARY',
		'NEW'			=> '_UNARY',
		'TYPEOF'		=> '_UNARY',
		'VOID'			=> '_UNARY',
		'U+'			=> '_UNARY',
		'U-'			=> '_UNARY',

		'++'			=> '_BOTH_ARY',
		'--'			=> '_BOTH_ARY',

		'='				=> '_ASSIGNMENT',
		'^'				=> '_OPERATOR',
		'^='			=> '_ASSIGNMENT',
		'|'				=> '_OPERATOR',
		'|='			=> '_ASSIGNMENT',
		'&'				=> '_OPERATOR',
		'&='			=> '_ASSIGNMENT',
		'+'				=> '_OPERATOR',
		'+='			=> '_ASSIGNMENT',
		'-'				=> '_OPERATOR',
		'-='			=> '_ASSIGNMENT',
		'*'				=> '_OPERATOR',
		'*='			=> '_ASSIGNMENT',
		'/'				=> '_OPERATOR',
		'/='			=> '_ASSIGNMENT',
		'%'				=> '_OPERATOR',
		'>'				=> '_OPERATOR',
		'>='			=> '_OPERATOR',
		'<'				=> '_OPERATOR',
		'<='			=> '_OPERATOR',
		'&&'			=> '_OPERATOR',
		'||'			=> '_OPERATOR',
		'=='			=> '_OPERATOR',
		'==='			=> '_OPERATOR',
		'!='			=> '_OPERATOR',
		'!=='			=> '_OPERATOR',
		'>>'			=> '_OPERATOR',
		'>>>'			=> '_OPERATOR',
		'>>>='			=> '_OPERATOR',
		'<<'			=> '_OPERATOR',
		'>>='			=> '_ASSIGNMENT',
		'<<='			=> '_ASSIGNMENT',

		'IN'			=> '_OPERATOR_PAREN',
		'INSTANCEOF'	=> '_OPERATOR_PAREN',

		'GET'			=> '_HASH_SETGET',
		'SET'			=> '_HASH_SETGET',

		'CONTINUE'		=> '_SELF_LEFT',
		'THIS'			=> '_SELF_LEFT',
	};

	do {
		no strict 'refs';
		*{__PACKAGE__ .'::__start_block'} = sub {
			unshift @Yasco::Node::block_stack, {'LINE' => $_[0],'FUNCTIONS_NAMES' => {},'SYMBOLS_NAMES' => {},'INLINEABLE' => {}}
		}
	};

	@Yasco::Node::block_stack = ();
	%Yasco::Node::inline_functions = ();
	Yasco::Node::__start_block();
}
##------------------------------------------------------------------------------
$Yasco::Node::NOP = undef;
##------------------------------------------------------------------------------
sub new
	{
		shift;
		my($op,$left,$right,$next) = @_;

		my $self = {
			id			=> $Yasco::Node::id++,
			op			=> $op,
			left		=> $left,
			right		=> $right,
			next		=> $next,
			pre_space	=> $Yasco::Lexer::whitespace,
			line		=> $Yasco::Main::instance->{p}->{LINE}
		};

		$Yasco::Lexer::whitespace = '';
		$self = bless $self, __PACKAGE__;

		## Reduce STMTS -> STMTS nesting
		REDUCE_STMTS: {
			last REDUCE_STMTS if 'STMTS' ne $self->op;
			last REDUCE_STMTS if !defined($self->left);
			$self->{left} = [grep {defined && 'NOP' ne $_->op} @{$self->{left}}];
			last REDUCE_STMTS if 1 != @{$self->left};
			eval {$self->left->[0]->op};
			if($@){print Dumper($self);die $@}

			$self->{left} = $self->{left}->[0]->{left}->{left},
			last REDUCE_STMTS if 'BLOCK' eq $self->left->[0]->op;

			$self->{left} = $self->{left}->[0]->{left},
			last REDUCE_STMTS if 'STMTS' eq $self->left->[0]->op;

		}

		foreach (qw(left right next)) {
			if (__PACKAGE__ eq ref $self->{$_}) {
				$self->{$_}->{parent} = \$self, next;
			}

			if ('ARRAY' eq ref $self->{$_}) {
				map {__PACKAGE__ eq ref $_ and $_->{parent} = \$self} @{$self->{$_}};
			}
		}

		if ('BLOCK' eq $op)
			{
				my $stck = shift @Yasco::Node::block_stack;
				foreach (qw(FUNCTIONS SYMBOLS)) {
					$self->{$_}= $stck->{$_."_NAMES"};
					delete $self->{$_} unless 0+keys %{$self->{$_}};
				}
			}

		if ('FUNC_DECL' eq $op)
			{
				if ('ID' eq $self->left->op)
					{
						$Yasco::Node::block_stack[0]->{FUNCTIONS_NAMES}->{$self->left->left}++;

						if ('BLOCK' eq $self->next->op &&
							'STMTS' eq $self->next->left->op &&
							'ARRAY' eq ref $self->next->left->left)
							{
								my $nStatements = 0+@{$self->next->left->left};
								my $nMaxStatements = $Yasco::Main::instance->option('inline_threshold') || 0;

								if ($nStatements <= $nMaxStatements)
									{
										$self->{__can_inline} = 1;
										$Yasco::Node::inline_functions{$self->left->left} = $self;
									}
							}
					}
			}

		if ('VAR' eq $op)
			{
				my @list = ((ref $self->left && 'ARRAY' ne ref $self->left) ? $self->left : @{$self->left || []});

				foreach my $node (@list)
					{
						my $var = do {
							for($node->op) {
								'ID' eq $_ and $node->left,last;
								'ASSIGN' eq $_ and $node->left->left,last;
								die 'VAR has "'.$node->op.'" on its left side: '. Dumper($node);
							}
						};

						$Yasco::Node::block_stack[0]->{SYMBOLS_NAMES}->{$var}++;
					}
			}

		if ('IF' eq $op && $self->o->option('optimize'))
			{
				$self = $self->optimizeIf;
			}

		$self->o->register_node(\$self);
		$self
	}
##------------------------------------------------------------------------------
sub NOP
	{
		$Yasco::Node::NOP = new Yasco::Node('NOP')
			unless defined($Yasco::Node::NOP);
		$Yasco::Node::NOP
	}
##------------------------------------------------------------------------------
sub o
	{
		$Yasco::Main::instance
	}
##------------------------------------------------------------------------------
sub reconstruct
	{
		my $self = shift;
		my %params = @_;
		my ($code,$sub);

		$Yasco::Node::nodes_processed++;
		$self->o->{_OB_DATA}->{scopes}->[0]->{node_depth}++;

		if (!$self->o->option(qq{quiet}) && 0 != (0x40 & $self->o->debug))
			{
				if ($Yasco::Node::nodes_lastperc < int(100 * $Yasco::Node::nodes_processed / $Yasco::Node::id))
					{
						$Yasco::Node::nodes_lastperc = int(100 * $Yasco::Node::nodes_processed / $Yasco::Node::id);
						printf STDERR "\r\x1B[K\t%d%%", $Yasco::Node::nodes_lastperc;
							}
					}

		$sub = $Yasco::Node::op_txl8->{$self->op} || $self->op;

		$self->o->push_insn($self->op);
		$code = $self->o->exec_filters($self);

		defined($code) or do {
			$code = eval '$self->_'.$sub.'(%params)';
			if ($@)
				{
					__PACKAGE__ eq ref $@ and die $@;
					print STDERR $@ . "$sub: ";## . Dumper($self);
					die $self;
				}
		};

		$self->o->pop_insn;

		$self->o->{_OB_DATA}->{scopes}->[0]->{node_depth}--;

		for ($self->{annotate}) {
			last unless defined;
			s/^\s+|\s+$//g;
			$code = "\n/* $_ */$code" if length;
		}

		$code = $self->pre_space . $code if $self->o->option(qq{white_space});
		$code
	}
##------------------------------------------------------------------------------
sub annotate
	{
		my $self = shift;
		return $self unless $self->o->option(qq{annotate});
		$self->{annotate} = sprintf $_[0], @_[1..$#_];
		return $self;
	}
##------------------------------------------------------------------------------
##
##	Returns the statement enclosed in a block if its the only statement in it:
##
##  if (...) {{{
##		foo();
##  }}}
##
##	Would return foo()
##
sub reduceBlock
	{
		my $self = shift;

		while ('BLOCK' eq $self->op)
			{
				last if 1 != @{$self->left->left};
				$self = $self->left->left->[0];
			}

		$self
	}
##------------------------------------------------------------------------------
sub _ARRAY
	{
		my $self = shift;
		my $code;

		$code = '[';
		$code .= join ',', map {$_->reconstruct} @{$self->left || []};
		$code .= ']';

		$code
	}
##------------------------------------------------------------------------------
sub _ARRAY_ACCESS
	{
		my $self = shift;
		my $code = $self->left->reconstruct;
		$self->o->push_insn('ARRAY_RIGHT_SIDE');
		$code .= '[' . $self->right->reconstruct . ']';
		$self->o->pop_insn;
		$code
	}
##------------------------------------------------------------------------------
sub _ASSIGN
	{
		my $self = shift;
		my ($pad,$code);
		my ($lhs,$rhs);

		$pad = $Yasco::Node::format ? ' ':'';

		$self->o->unshift_stack('RHS');
		$rhs = $self->right->reconstruct;
		$self->o->shift_stack;

		$self->o->unshift_stack('LHS');
		$lhs = $self->left->reconstruct;
		$self->o->shift_stack;

		$code = "$lhs$pad=$pad$rhs";

		$code .= "\n" if (4 & $self->o->option(qq{newlines}));
		$code
	}
##------------------------------------------------------------------------------
sub _BLOCK
	{
		my $self = shift;
		my %params = @_;
		my $code;

		return $self->left->reconstruct
			if exists $params{unwrap};

		$self->o->enter_scope(undef, 'functions', $self->{FUNCTIONS});
		$self->o->enter_scope(undef, 'symbols', $self->{SYMBOLS});

		$code = $self->left->reconstruct;
		$code =~ s/\n+$//;

		if ($Yasco::Node::format)
			{
				$code = [split /\n/, $code];
				$code = "\n\t{\n" .
						join("\n", map {"\t\t$_"} @$code) .
						"\n\t}";
				return $code;
			}

		$code =~ s/;+\s*$//;
		$code = '{' . $code . '}';
		$code .= "\n" if (2 & $self->o->option(qq{newlines}));
		$code
	}
##------------------------------------------------------------------------------
sub _CALL
	{
		my $self = shift;
		my $code;
		my $func = $self->left->left;

		if (exists $Yasco::Node::inline_functions{$func})
			{
				$func = $Yasco::Node::inline_functions{$func};
				my ($n,$body,%params,%replacement);

				if (defined($self->right))
					{
						die if 'LIST' ne $self->right->[0]->op;

						$n = 0;
						$params{$n++} = $_->left foreach @{$func->right};

						$n = $self->right->[0]->left;

						foreach my $argno (0..$#$n)
							{
								next unless exists $params{$argno};	# specified more parameters to function than it accepts
								$replacement{$params{$argno}} = $n->[$argno]->reconstruct;
							}

						# fill skipped parameters w/ "null" value
						(exists $replacement{$_} or $replacement{$_} = 'null') foreach values %params;
					}

				for (;;) {
					local $self->o->{filters};

					$self->o->{filters}->{ID} ||= [];
					unshift @{$self->o->{filters}->{ID}}, Yasco::Filter::Id2Value->new(\%replacement);

					REBUILD_BODY: {
						$body = $func->next->left->left->[0]->reconstruct,
						last REBUILD_BODY if 1 == 0+@{$func->next->left->left};

						$body = $func->next->reconstruct;
						$body = substr $body, 1, -2;
					}

					return $body;
				}
			}

		$code = $self->left->reconstruct;

		$self->o->push_insn('PAREN');
		$code .= '(';
		$code .= join ',', map{$_->reconstruct} @{$self->right || []};
		$code .= ')';
		$self->o->pop_insn;

		$code
	}
##------------------------------------------------------------------------------
sub _CASE
	{
		my $self = shift;
		'case ' . $self->left->reconstruct . ':' . $self->right->reconstruct
	}
##------------------------------------------------------------------------------
sub _CLOSURE
	{
		my $self = shift;
		my $code;

		$code = 'function(';
		$self->o->enter_scope($self, 'collect');

		ref $self->left and $code .= $self->left->reconstruct;

		$self->o->enter_scope($self, 'find');
		$code .= ')' . $self->right->reconstruct;
		$self->o->leave_scope(2);

		$code .= "\n" if (1 & $self->o->option(qq{newlines}));
		$code
	}
##------------------------------------------------------------------------------
sub _CONSTANT
	{
		my $self = shift;

		return $self->left
			if $self->o->option('deobfuscate');

		for (lc $self->left) {
			'true' eq $_ and	return '!0';
			'false' eq $_ and	return '!1';
		}

		$self->left
	}
##------------------------------------------------------------------------------
sub _DEFAULT
	{
		my $self = shift;
		'default:' . $self->right->reconstruct
	}
##------------------------------------------------------------------------------
sub _DELETE
	{
		my $self = shift;
		'delete '.$self->left->reconstruct;
	}
##------------------------------------------------------------------------------
sub _DO
	{
		my $self = shift;
		my $c = 'do';

		$c .= ' ' unless 'BLOCK' eq $self->left->op;
		$c .= $self->left->reconstruct;
		$c .= 'while(' .$self->right->reconstruct.')';

		$c
	}
##------------------------------------------------------------------------------
sub _DOT
	{
		my $self = shift;
		my $code = $self->left->reconstruct;

		$self->o->push_insn('DOT_RIGHT_SIDE');
		$code .= $Yasco::Node::DOT_GLUE . $self->right->reconstruct;
		$self->o->pop_insn;

		$code
	}
##------------------------------------------------------------------------------
sub _FOR
	{
		my $self = shift;
		my $code = 'for';

		$code .= '(' . $self->left->reconstruct . ')';
		$code .= $self->right->reduceBlock->reconstruct if ref $self->right;

		if ('FOR_LIST' eq $self->left->op) {
			$self->o->leave_scope(2); # 2 scopes are started in _FOR_LIST
		}

		$code
	}
##------------------------------------------------------------------------------
sub _FOR_LIST
	{
		my $self = shift;
		my @c;

		$self->o->enter_scope($self, 'collect');
		push @c, ''						unless defined $self->left;
		push @c, $self->left->reconstruct	if defined $self->left;
		$self->o->enter_scope($self, 'find');
		push @c, ''						unless defined $self->right;
		push @c, $self->right->reconstruct	if defined $self->right;

		push @c, ''						unless defined $self->next;
		push @c, $self->next->reconstruct	if defined $self->next;

		join ';', @c;
	}
##------------------------------------------------------------------------------
sub _FUNC_DECL
	{
		my $self = shift;

		return '' if $self->{__can_inline};

		my $code = 'function ';

		$self->o->scope_mode('collect_vars');
		$code .= $self->left->reconstruct;
		$self->o->scope_mode('find');

		$self->o->enter_scope($self, 'collect');
		$code .= '()' unless defined($self->right);
		$code .= '('.$self->right->reconstruct.')' if defined($self->right);
		#$code .= '(' . join(',', map {$_->reconstruct} @{$self->right || []}) . ')';
		$self->o->enter_scope($self, 'find');
		$code .= $self->next->reconstruct;
		$self->o->leave_scope(2);

		$code
	}
##------------------------------------------------------------------------------
sub _HASH
	{
		my $self = shift;
		my ($id,$code);
		my $joiner = (1 & $self->o->option(qq{newlines})) ? ",\n" : ",";

		$code = [map {$_->reconstruct} @{$self->left || []}];
		$code = '{' . join($joiner, @$code) . '}';

		$code
	}
##------------------------------------------------------------------------------
sub _ID
	{
		my $self = shift;
		$self->left
	}
##------------------------------------------------------------------------------
sub _IF
	{
		my $self = shift;
		my $code;

		local($self->{right}) = $self->right->reduceBlock if defined($self->right);
		local($self->{next}) = $self->next->reduceBlock if defined($self->next);

		return $self->left->reconstruct
			if !defined($self->right) && !defined($self->next);

		$code  = 'if';
		$code .= ' ' if $Yasco::Node::format;
		$code .= '(' . $self->left->reconstruct . ')';

		#$code .= sprintf("\t\t\t\t\t// [%d:(%s)%d]\n", $self->id, $self->right->op, $self->right->needsSemicolon || 0);

		$code .= "\n" if $Yasco::Node::format || (1 & $self->o->option(qq{newlines}));
		$code .= "\t" if $Yasco::Node::format;

		if (ref $self->right)
			{
				my $rightSemi = $self->right->needsSemicolon;

				## Protect own "else" block
				if (ref $self->next && 'IF' eq $self->right->op)
					{
						$code .= '{';
						$code .= "\n" if $Yasco::Node::format || (1 & $self->o->option(qq{newlines}));
						$code .= $self->right->reconstruct;
						$code .= "\n" if $Yasco::Node::format || (1 & $self->o->option(qq{newlines}));
						$code .= '}';
					}
				else
					{
						$code .= $self->right->reconstruct;
						$code .= ';' if $rightSemi && ref $self->next;
					}
			}

		if (ref $self->next)
			{
				$code .= ';' unless ref $self->right;
				$code .= 'else';
				$code .= ' ' if 'BLOCK' ne $self->next->op;
				$code .= "\n" if (1 & $self->o->option(qq{newlines}));
				$code .= $self->next->reconstruct;
			}

		$code;
	}
##------------------------------------------------------------------------------
sub _INLINE
	{
		my $self = shift;

				$self->left->reconstruct.
		'?'	.	$self->right->reconstruct.
		':'	.	$self->next->reconstruct
	}
##------------------------------------------------------------------------------
sub _KV
	{
		my $self = shift;
		my $code;

		do {
			local($self->o->{no_id_mapping}) = 1;
			local($self->o->{_OB_DATA}->{map_string}) = 0;
			$code = $self->left->reconstruct;
		};

		$code .= ':';
		$code .= $self->right->reconstruct;

		$code
	}
##------------------------------------------------------------------------------
sub _LABEL
	{
		my $self = shift;

		$self->o->enter_scope($self, 'collect');

		my $code = $self->left->reconstruct .':';
		$code .= "\n" if (0x11 & $self->o->option(q{newlines}));

		$self->o->enter_scope($self, 'find');
		$code .= $self->right->reconstruct;
		$self->o->leave_scope(2);

		$code;
	}
##------------------------------------------------------------------------------
sub _LIST
	{
		my $self = shift;
		my $joiner = ((2 & $self->o->option(qq{newlines})) ? ",\n" : ',');

		join $joiner, map {$_->reconstruct} @{$self->left};
	}
##------------------------------------------------------------------------------
sub _NUMBER
	{
		my $self = shift;

		for ($self->left) {
			m{^0x[a-f0-9]+}i	and return sprintf('%d', hex $self->left);
			m{^0[0-7]+$}		and return sprintf('%d', oct $self->left);
		}

		$self->left
	}
##------------------------------------------------------------------------------
sub _PAREN
	{
		my $self = shift;
		'(' . ($self->left ? $self->left->reconstruct : '') . ')'
	}
##------------------------------------------------------------------------------
sub _PARAMETER_LIST
	{
		my $self = shift;

		join(',', map {$_->reconstruct} @{$self->left || []})
	}
##------------------------------------------------------------------------------
sub _RETURN
	{
		my $self = shift;
		my $code = 'return';
		$code . ($self->left ? ' ' . $self->left->reconstruct : '');
	}
##------------------------------------------------------------------------------
sub _BREAK
	{
		my $self = shift;
		my $code = 'break';
		$code . ($self->left ? ' ' . $self->left->reconstruct : '');
	}
##------------------------------------------------------------------------------
sub _STMTS
	{
		my $self = shift;
		my ($code,$nodeCount,@stmts);

		$self->o->enter_scope(undef, 'functions', $self->{FUNCTIONS});
		$self->o->enter_scope(undef, 'symbols', $self->{SYMBOLS});

		$nodeCount = $#{$self->left || []};

		STATEMENTS: foreach my $n (0..$nodeCount)
			{
				my ($p,$t,$r) = ('','');
				my $node = $self->{left}->[$n];

				#push(@stmts,'/*empty*/'),
				next STATEMENTS
					unless defined($node);

				if ($n == $nodeCount || !$node->needsSemicolon)
					{
						$p = '';
					}
				else
					{
						$p = ';';
						#$p .= '/*'.$node->op.'*/';
					}

				$p .= "\n" if $Yasco::Node::format;
				$r = $node->reconstruct;
				push @stmts, $t.$r.$p;
			}

		$code = join(((1 & $self->o->option(qq{newlines})) ? "\n" : ''), @stmts);
		$code .= "\n" if (1 & $self->o->option(qq{newlines}));

		$code
	}
##------------------------------------------------------------------------------
sub _STRING
	{
		my $self = shift;
		$self->left;
	}
##------------------------------------------------------------------------------
sub _SWITCH
	{
		my $self = shift;
		my ($code,$joiner);
		my $caseCreate = sub {
			my ($node, $isLast) = @_;
			$node->reconstruct . ($isLast ? '' : (!(ref $node->right->left) || !$node->right->left->[-1]->needsSemicolon ? '' : ';'));
		};

		$joiner = ((2 & $self->o->option(qq{newlines})) ? "\n" : '');
		$code = 'switch(' . $self->left->reconstruct . '){';
		$code .= $joiner;
		$code .= join $joiner, map {$caseCreate->($_, $_ == $self->right->[-1])} @{$self->right};
		$code .= '}';
		$code .= $joiner;
		$code
	}
##------------------------------------------------------------------------------
sub _THROW
	{
		my $self = shift;
		'throw '.$self->left->reconstruct
	}
##------------------------------------------------------------------------------
sub _TRY
	{
		my $self = shift;
		my $code = 'try';

		$code .= ' ' if 'BLOCK' ne $self->left->op;
		$code .= $self->left->reconstruct;
		$code .= $self->right->reconstruct if $self->right;
		$code .= $self->next->reconstruct if $self->next;

		$code
	}
##------------------------------------------------------------------------------
sub _TRY_CATCH
	{
		my $self = shift;
		my $code = 'catch(';

		$self->o->enter_scope($self, 'collect');
			$code .= $self->left->reconstruct . ')';
		$self->o->enter_scope($self, 'find');
			$code .= $self->right->reconstruct;
		$self->o->leave_scope(2);

		$code
	}
##------------------------------------------------------------------------------
sub _TRY_FINALLY
	{
		my $self = shift;

		'finally' . $self->left->reconstruct;
	}
##------------------------------------------------------------------------------
sub _T_REGEX
	{
		my $self = shift;
		$self->left;
	}
##------------------------------------------------------------------------------
sub _VAR
	{
		my $self = shift;
		my $code = 'var ';
		my $joiner = (1 & $self->o->option(qq{newlines})) ? ",\n" : ",";
		my $list = $self->left;

#	not A Good Thing:
#	var a = 1+2*a; => var v0=1+2*v0.
		'ARRAY' ne ref $list and $list = [$list];

		$self->o->scope_mode('collect_vars');
		$code .= join $joiner, map {$_->reconstruct} @$list;
		$self->o->scope_mode('find');
		$code
	}
##------------------------------------------------------------------------------
sub _WHILE
	{
		my $self = shift;
		my $c = 'while(' . $self->left->reconstruct .')';
		if (defined($self->right)) {
			$c .= $self->right->reconstruct;
			$c .= ';' if 'BLOCK' ne $self->right->op;
		} else {
			$c .= ';';
		}
		$c
	}
##------------------------------------------------------------------------------
sub _WITH
	{
		my $self = shift;
		my $code = 'with('.$self->left->reconstruct.')';
		$code .= $self->right->reconstruct;
		$code .= ';' if 'BLOCK' ne $self->right->op;
		$code
	}
##------------------------------------------------------------------------------
sub __BOTH_ARY
	{
		my $self = shift;

		$self->left
		?	$self->op . $self->left->reconstruct
		:	$self->right->reconstruct . $self->op
	}
##------------------------------------------------------------------------------
sub __HASH_SETGET
	{
		my $self = shift;
		my $code = lc($self->op).' ';
		do {
			local($self->o->{no_id_mapping}) = 1;
			$code .= $self->left->reconstruct;
		};
		$code .= '('.(defined($self->right) ? $self->right->reconstruct : '').')';
		$code .= $self->next->reconstruct;
		$code;
	}
##------------------------------------------------------------------------------
sub __ASSIGNMENT
	{
		my $self = shift;
		my $code = '';

		$self->o->push_insn('LHS');
		$code .= $self->left->reconstruct;
		$self->o->pop_insn;

		$code .= $Yasco::Node::format ? ' ':'';
		$code .= $self->op;
		$code .= $Yasco::Node::format ? ' ':'';

		$code .= $self->right->reconstruct;

		$code
	}
##------------------------------------------------------------------------------
sub __OPERATOR
	{
		my $self = shift;
		my (@stack,@opStack,@argStack);

		return $self->op . $self->left->reconstruct
			if ('-' eq $self->op and !defined($self->right));
		my $pad = ($Yasco::Node::format ? ' ' : '');

		if ($self->o->option('optimize')) {
			GET_RESULT: {

				last GET_RESULT if $self->op =~ m{^[=!]==?|[<>]=?|\|\||&&$};

				my $result = eval {$self->result};
				return $result->{value} unless $@;
				warn $@ unless ref $@;
				warn $@->[0] if 'ARRAY' eq ref $@ and 0+@{$@};
			}
		}

		return $self->left->reconstruct . $pad . $self->op . $pad . $self->right->reconstruct;
	}
##------------------------------------------------------------------------------
sub result
	{
		my $self = shift;
		my ($l,$r);

		local($self->{line}) = 1 + $self->{line};

		if ('_OPERATOR' eq ($Yasco::Node::op_txl8->{$self->op} || '<none>'))
			{
				$l = $self->left->result;
				$r = $self->right->result;

				if ('+' eq $self->op)
					{
						if ('numbernumber' ne $l->{type} . $r->{type})
							{
								my $delimiter = substr('string' eq $l->{type} ? $l->{value} : $r->{value}, 0, 1);

								$l = ('string' eq $l->{type} ? substr($l->{value}, 1, -1) : $l->{value});
								$r = ('string' eq $r->{type} ? substr($r->{value}, 1, -1) : $r->{value});

								return {type => 'string', value => $delimiter . $l . $r . $delimiter};
							}

						return {type => 'number', value => $l->{value} + $r->{value}};
					}

				die sprintf('Near line %d: incompatible operands for "%s": %s(%s) vs. %s(%s)', $self->{line}, $self->op,
					$l->{type}, $l->{value},
					$r->{type}, $r->{value})
					if 'numbernumber' ne $l->{type} . $r->{type};

				($l,$r) = ($l->{value},$r->{value});

				return {type => 'number', value => $l - $r} if '-' eq $self->op;
				return {type => 'number', value => $l * $r} if '*' eq $self->op;
				return {type => 'number', value => $l / $r} if '/' eq $self->op;
				return {type => 'number', value => $l % $r} if '%' eq $self->op;
				return {type => 'number', value => $l ^ $r} if '^' eq $self->op;
				return {type => 'number', value => $l & $r} if '&' eq $self->op;
				return {type => 'number', value => $l | $r} if '|' eq $self->op;
				return {type => 'number', value => $l >> $r} if '>>' eq $self->op;
				return {type => 'number', value => $l << $r} if '<<' eq $self->op;

				die sprintf('Near line %d: cannot calculate result of "%s %s %s"', $self->{line}, $l, $self->op, $r);
			}

		for ($self->op) {
			'NUMBER' eq $_ and return {type => 'number', value => $self->left};
			'STRING' eq $_ and return {type => 'string', value => $self->left};
			'CONSTANT' eq $_ and return {type => 'const', value => $self->left};
			'PAREN' eq $_ and do {
				my $result = eval {$self->left->result};
				die $@ if ref $@;
				die sprintf('line %d: paren operator failed to calculate: %s', $self->{line}, $@) if $@;
				return $result;
			};
			'U-' eq $_ and do {
				my $result = eval {$self->left->result};
				die sprintf('line %d: paren operator failed to calculate: %s', $self->{line}, $@) if $@;
				$result->{value} *= -1;
				return $result;
			};

			m{^
				ARRAY_ACCESS|CALL|DOT|ID|INLINE|TYPEOF
			$}x and die[];

			die[sprintf('near line %d: cannot get value of "%s" operator.', $self->{line}, $self->{op})];
			die Dumper({OPERAND => $self});
		}
	}
##------------------------------------------------------------------------------
sub __OPERATOR_PAREN
	{
		my $self = shift;
		my $code = $self->left->reconstruct;

		$code .= ' ' if 'PAREN' ne $self->left->op;
		$code .= lc($self->op);
		$code .= ' ' if 'PAREN' ne $self->right->op;
		$code .= $self->right->reconstruct;
		$code
	}
##------------------------------------------------------------------------------
sub __SELF_LEFT
	{
		my $self = shift;
		$self->left || lc($self->op)
	}
##------------------------------------------------------------------------------
sub __UNARY
	{
		my $self = shift;
		my $code = lc($self->op);
		$code = $1 if $code =~ m{^u([-+])$};

		$code .= ' ' unless $code =~ m/^[-!+]$/ or 'PAREN' eq $self->left->op;
		$code .= $self->left->reconstruct;
		$code
	}
##------------------------------------------------------------------------------
sub dump
	{
		print STDERR "\n\n";
		$_[0]->_dump(0);
	}

sub _dump
	{
		my $self = shift;
		my $level = shift;

		printf STDERR "%s\t%s[%s]", $self->id, ('    ' x $level), $self->op;

		for ($self->op) {
			m{^CONSTANT|ID|NUMBER|STRING$}
				and return printf STDERR " %s\n", $self->left;

			'CALL' eq $_ and do {
				print STDERR "\n";
				$self->left->_dump(1 + $level);
				local($Yasco::Node::nodes_lastperc) = 200;
				local($Yasco::Node::nodes_processed) = 0;
				printf STDERR "%s[ARGS]=(%s)\n", ('    ' x (1+$level)), ref $self->right ? join(',', map {$_->reconstruct} @{$self->right}) : '';
				return;
			};

			'STMTS' eq $_ and do {
				return print STDERR "<empty>\n"
					unless $self->{left} && @{$self->{left}};
				print STDERR " (...)\n";

				$_->_dump(1 + $level)
					foreach @{$self->{left}};
				return;
			};

			print STDERR "\n";

		}

		foreach (qw(left right next)) {
			next unless defined($self->{$_});
			$self->{$_}->_dump(1+$level), next if __PACKAGE__ eq ref $self->{$_};
			#print STDERR join("\n", map {('    'x$level).$_} split(/\n/, Dumper($self->{$_})));
		}
	}
##------------------------------------------------------------------------------
sub optimizeIf
	{
		my $self = shift;

		my $hasBlock = defined($self->{right}) && 'NOP' ne $self->{right}->op;
		my $hasElse = defined($self->{next}) && 'NOP' ne $self->{next}->op;

		my ($right,$next,$node);

		$right = $self->right->reduceBlock if $hasBlock;
		$next = $self->next->reduceBlock if $hasElse;

		#	INPUT					OUTPUT
		#	if (arg);		->		arg;
		return $self->left unless $hasBlock || $hasElse;

		#	Optimizes if(0), if(1), if(true) and if(false)

		if ('NUMBER' eq $self->left->op)
			{
				$self->annotate('IF[@%d]', __LINE__);
				return $self->left->left
					? ($hasBlock ? $right : NOP())
					: ($hasElse ? $next : NOP());
			}

		if ('CONSTANT' eq $self->left->op)
			{
				$self->annotate('IF[@%d]', __LINE__);
				return ('true' eq $self->left->left)
					? ($hasBlock ? $right : NOP())
					: ($hasElse ? $next : NOP());
			}

		#	if (a) if (b) foo()		if(a && b) foo()
		if ($hasBlock && !$hasElse)
			{
				if ('IF' eq $right->op && !defined($right->next))
					{
						$self->annotate('IF[@%d]', __LINE__);
						$self->{left} = new Yasco::Node('&&', $self->left, $right->left);
						$self->{right} = $right->right;
					}
			}

		#	if (arg)				arg && foo();
		#		foo();
		#
		#	OR
		#
		#	if (arg)				!(arg) && foo();
		#		;
		#	else
		#		foo();

		OPT_SINGLE_CHILD: {
			last OPT_SINGLE_CHILD if $hasBlock && $hasElse;
			my $node = $hasBlock ? $right : $next;
			last OPT_SINGLE_CHILD if $node->op =~ m{^BLOCK|BREAK|CONTINUE|DO|FOR|IF|WHILE|WITH|RETURN|STMTS|THROW|TRY$};

			$self->annotate('IF[@%d](op:%s)', __LINE__, $node->op);
			$self->{op} = '&&';

			if ($hasBlock)
				{
					$self->{right} = $right->isSingleValue ? $right : $right->encloseNode;
					$self->{left} = $self->{left}->encloseNode
						unless $self->{left}->isSingleValue;
				}
			else
				{
					$self->{right} = $self->{next};
					delete $self->{next};
					$self->{left} = $self->{left}->negateNode;
				}

			return $self;
		}

		#	if (arg)				if(!arg) {foo();bar()}
		#		;
		#	else
		#		{
		#			foo();
		#			bar();
		#		}

		OPT_ELSE_ONLY: {
			last OPT_ELSE_ONLY if $hasBlock;
			last OPT_ELSE_ONLY if 'IF' eq $next->op && defined($next->next);

			$self->annotate('IF[@%d,%s]', __LINE__, $next->op);
			$self->{left} = $self->left->negateNode;
			$self->{right} = $next;
			delete $self->{next};
			return $self;
		}

		#	if (arg)				if(arg) return 'Foo'; ...
		#		return 'Foo';
		#	else ...
		#
		#	OR
		#
		#	if (arg)				if(!arg) return 'Foo'; ...
		#		...
		#	else
		#		return 'Foo';

		OPT_USELESS_ELSE: {
			last OPT_USELESS_ELSE unless $hasBlock && $hasElse;
			last OPT_USELESS_ELSE unless defined($self->{parent});

			if ('RETURN' eq $right->op)
				{
					$node = $next;
				}
			elsif ('RETURN' eq $next->op)
				{
					$node = $right;
					$self->{left} = $self->left->negateNode;
					$self->{right} = $next;
				}
			else
				{
					last OPT_USELESS_ELSE;
				}

			delete $self->{next};

			if ('BLOCK' eq $node->op)
				{
					die 'Something aint right' if 'STMTS' ne $node->left->op;
					$node = $node->{left};
					unshift @{$node->{left}}, $self;

					return $node;
				}

			return (new Yasco::Node('STMTS', [$self, $node]))->annotate('IF[@%d]', __LINE__);
		}

		#	if (arg)			arg ? foo() : bar()
		#		foo();
		#	else
		#		bar();
		OPT_ELSE_ONELINER: {
			last OPT_ELSE_ONELINER unless $hasBlock && $hasElse;
			last OPT_ELSE_ONELINER unless $right->isSingleValue;
			last OPT_ELSE_ONELINER unless $next->isSingleValue;

			$self->annotate('IF[@%d]', __LINE__);
			$self->{op} = 'INLINE';
			$self->{right} = $right;
			$self->{next} = $next;
			return $self;
		}

		$self;
	}
##------------------------------------------------------------------------------
sub encloseNode
	{
		new Yasco::Node('PAREN', $_[0]);
	}
##------------------------------------------------------------------------------
sub negateNode
	{
		my $node = new Yasco::Node('!');

		$node->{left} = $_[0], return $node if $_[0]->isSingleValue;
		$node->{left} = $_[0]->encloseNode;
		$node
	}
##------------------------------------------------------------------------------
sub isSingleValue
	{
		my $self = shift;
		return $self->op =~ m{^ID|NUMBER|CONSTANT|STRING|CALL};
	}
##------------------------------------------------------------------------------
sub isMultiStatementBlock
	{
		my $self = shift;

		return if 'BLOCK' ne $self->op;
		return defined($self->left->left) && 1 < @{$self->left->left};
	}
##------------------------------------------------------------------------------
sub needsSemicolon
	{
		my $self = shift;

		for ($self->op) {
			return 0 if m{^BLOCK|DO|FUNC_DECL|WITH|SWITCH|TRY$};
			return (defined($self->next) ? $self->next : $self->right)->reduceBlock->needsSemicolon
					 if 'IF' eq $_;

			return 0 if 'FOR' eq $_ && ref $self->right && !$self->right->reduceBlock->needsSemicolon;
			return 0 if 'WHILE' eq $_ && ref $self->right && !$self->right->reduceBlock->needsSemicolon;
		}


		1
	}
##------------------------------------------------------------------------------
1